package org.budo.accesslog.config;

import java.util.List;

import org.budo.accesslog.aop.interceptor.BudoAccesslogAopInterceptor;
import org.budo.accesslog.mvc.interceptor.BudoAccesslogHandlerInterceptor;
import org.budo.accesslog.repository.AccesslogRepository;
import org.budo.accesslog.servlet.filter.BudoAccesslogServletFilter;
import org.budo.accesslog.web.api.AccesslogApiImpl;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.mvcs.MvcsHandlerInterceptor;
import org.budo.support.spring.aop.advisor.BudoAopPointcutAdvisor;
import org.budo.support.spring.bean.factory.support.BeanBuilder;
import org.budo.support.spring.context.aware.RootApplicationContextRefreshedEventListener;
import org.budo.support.spring.util.SpringUtil;
import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.handler.MappedInterceptor;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@Getter
@Setter
@Slf4j
public class BudoAccesslogConfigurer extends RootApplicationContextRefreshedEventListener implements BeanDefinitionRegistryPostProcessor {
    private static final String MAPPED_ACCESSLOG_MVC_INTERCEPTOR_ID = "mapped-budo-accesslog-mvc-interceptor";

    private static final String ACCESSLOG_HANDLER_INTERCEPTOR_ID = "BudoAccesslogHandlerInterceptor";

    private static final String ACCESSLOG_AOP_INTERCEPTOR_ID = "BudoAccesslogAopInterceptor";

    private static final String MAPPED_MVCS_HANDLER_INTERCEPTOR_ID = "mapped-MvcsHandlerInterceptor";

    private static final String MVCS_HANDLER_INTERCEPTOR_ID = "MvcsHandlerInterceptor";

    protected static final String AUTO = "auto";

    /**
     * 指定Aop过滤的作用范围
     */
    private String aopPatterns;

    /**
     * 指定Mvc过滤的作用范围
     */
    private String mvcPatterns;

    /**
     * 为 true 表示，仅处理有注解的，没注解就不处理
     */
    private Boolean annotation = true;

    private String dataSourceBeanName;

    private String repositoryBeanName;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        log.info("#19 postProcessBeanFactory, beanFactory=" + ObjectUtils.identityToString(beanFactory) + ", this=" + ObjectUtils.identityToString(this));
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        log.info("#24 postProcessBeanDefinitionRegistry, registry=" + ObjectUtils.identityToString(registry) + ", this=" + ObjectUtils.identityToString(this));

        this.registryWebInterceptor(registry);
        this.registryAopInterceptor(registry);

        this.registryMvcsHandlerInterceptor(registry);

        this.registryAccesslogApi(registry);
    }

    private void registryAccesslogApi(BeanDefinitionRegistry registry) {
        new BeanBuilder() //
                .type(AccesslogApiImpl.class) //
                .registerTo(registry);
    }

    private void registryMvcsHandlerInterceptor(BeanDefinitionRegistry registry) {
        new BeanBuilder() //
                .id(MVCS_HANDLER_INTERCEPTOR_ID) //
                .type(MvcsHandlerInterceptor.class) //
                .registerTo(registry);

        // Mvc Handler Map
        new BeanBuilder() //
                .id(MAPPED_MVCS_HANDLER_INTERCEPTOR_ID) //
                .type(MappedInterceptor.class) //
                .constructorArg(null) // includePatterns
                .constructorArgReference(MVCS_HANDLER_INTERCEPTOR_ID) // interceptor
                .registerTo(registry);
    }

    private void registryAopInterceptor(BeanDefinitionRegistry registry) {
        if (StringUtil.isNullOrEmpty(this.getAopPatterns())) {
            log.warn("#109 registryAopInterceptor return, aopPatterns is null, this=" + this);
            return;
        }

        BeanBuilder beanBuilder = new BeanBuilder() //
                .id(ACCESSLOG_AOP_INTERCEPTOR_ID) //
                .type(BudoAccesslogAopInterceptor.class);

        this.accesslogInterceptorProperties(beanBuilder);

        beanBuilder.registerTo(registry);

        if (AUTO.equalsIgnoreCase(this.getAopPatterns())) { // 自动Aop
            new BeanBuilder() //
                    .id("budo-accesslog-advisor") //
                    .type(BudoAopPointcutAdvisor.class) //
                    .ref("advice", ACCESSLOG_AOP_INTERCEPTOR_ID)//
                    .registerTo(registry);
        } else { // 指定Aop范围
            new BeanBuilder() //
                    .id("budo-accesslog-aspectj") //
                    .type(AspectJExpressionPointcutAdvisor.class) //
                    .property("expression", this.getAopPatterns()) //
                    .ref("advice", ACCESSLOG_AOP_INTERCEPTOR_ID) //
                    .registerTo(registry);
        }
    }

    private void registryWebInterceptor(BeanDefinitionRegistry registry) {
        if (StringUtil.isNullOrEmpty(this.getMvcPatterns())) {
            log.warn("#93 registryWebInterceptor return, mvcPatterns is null, this=" + this);
            return;
        }

        BeanBuilder beanBuilder = new BeanBuilder() //
                .id(ACCESSLOG_HANDLER_INTERCEPTOR_ID) //
                .type(BudoAccesslogHandlerInterceptor.class);

        this.accesslogInterceptorProperties(beanBuilder);

        beanBuilder.registerTo(registry);

        String includePatterns = null;
        if (AUTO.equalsIgnoreCase(this.getMvcPatterns())) {
            includePatterns = null;
        } else {
            includePatterns = this.getMvcPatterns();
        }

        // Mvc Handler Map
        new BeanBuilder() //
                .id(MAPPED_ACCESSLOG_MVC_INTERCEPTOR_ID) //
                .type(MappedInterceptor.class) //
                .constructorArg(includePatterns) // includePatterns
                .constructorArgReference(ACCESSLOG_HANDLER_INTERCEPTOR_ID) // interceptor
                .registerTo(registry);
    }

    @Override
    protected void onRootApplicationContextRefreshedEvent(ContextRefreshedEvent contextRefreshedEvent) {
        log.info("#29 onRootApplicationContextRefreshedEvent, contextRefreshedEvent=" + contextRefreshedEvent + ", this=" + this);

        ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
        this.registerServletFilter(applicationContext);
    }

    private void registerServletFilter(ApplicationContext applicationContext) {
        List<BeanDefinition> definitions = SpringUtil.getBeanDefinitionListByType(applicationContext, AccesslogRepository.class.getName());
        if (null != definitions && !definitions.isEmpty()) {
            log.info("#139 AccesslogRepository beanDefinitions=" + definitions);
            return;
        }

        BeanBuilder beanBuilder = new BeanBuilder() //
                .id("BudoAccesslogServletFilter") //
                .type(BudoAccesslogServletFilter.class);

        this.accesslogInterceptorProperties(beanBuilder);

        beanBuilder.registerTo(applicationContext);
    }

    protected void accesslogInterceptorProperties(BeanBuilder beanBuilder) {
        if (null != this.getAnnotation()) {
            beanBuilder.property("annotation", this.getAnnotation());
        }

        if (null != this.getDataSourceBeanName()) {
            beanBuilder.ref("dataSource", this.getDataSourceBeanName());
        }

        if (null != this.getRepositoryBeanName()) {
            beanBuilder.ref("repositoryService", this.getRepositoryBeanName());
        }
    }

    @Override
    public String toString() {
        return this.getClass().getName() + "@" + Integer.toHexString(this.hashCode()) //
                + ", aopPatterns=" + this.getAopPatterns() //
                + ", mvcPatterns=" + this.getMvcPatterns() //
                + ", annotation=" + this.getAnnotation() //
                + ", dataSourceBeanName=" + this.getDataSourceBeanName() //
                + ", repositoryBeanName=" + this.getRepositoryBeanName() //
                + super.toString();
    }
}